import numpy as np
import pandas as pd
import time
import torch
from tqdm import tqdm

class MyTools():

    def __init__(self, click_df, user_colname, item_colname, time_colname):
        self.df = click_df
        self.user_colname = user_colname
        self.item_colname = item_colname
        self.time_colname = time_colname
        self.uit = self.get_user_item_time()
        self.user_dtime, self.user_item, self.all_dtime = self.d_time()
    
    def get_user_item_time(self):
        """
        click_df: 用户行为数据 dataframe格式
        user_colname: 字符串, click_df中userid的列名
        item_colname: 字符串, click_df中itemid的列名
        time_colname: 字符串, click_df中点击时间的列名
        """
        self.df = self.df.sort_values(self.time_colname)
    
        def make_item_time_pair(df):
            return list(zip(df[self.item_colname], df[self.time_colname]))
    
        user_item_time_df = self.df.groupby(self.user_colname)[self.item_colname, self.time_colname]\
                            .apply(lambda x: make_item_time_pair(x)).reset_index().rename(columns={0: 'item_time_list'})
        user_item_time_dict = dict(zip(user_item_time_df[self.user_colname], user_item_time_df['item_time_list']))
    
        return user_item_time_dict
    
    def d_time(self):
        #获取样本时间差，方便后期筛选关联item对（item1，item2）
        user_dtime = {}
        user_item = {}
        all_dtime=[]
        for user in self.uit:
            dtime = []
            items = []
            for item, time in self.uit[user]:
                dtime.append(time)
                items.append(item)
            dtime = np.array(dtime[1:])-np.array(dtime[:-1])
            user_dtime[user] = dtime
            user_item[user] = items
            all_dtime += dtime.tolist()
        return user_dtime, user_item, np.array(all_dtime)

    #获得规定时间差内的item序列
    def user_behavior_sequences(self, percent):
        dtime = np.percentile(self.all_dtime, percent)
        u_behavior_i2i = [] #(item1, itme2) 认为item1选择后会对item2产生影响
        for user in self.user_dtime:
            for i, time in enumerate(self.user_dtime[user]):
                if time <= dtime:
                    u_behavior_i2i.append((self.user_item[user][i], self.user_item[user][i+1]))
        return u_behavior_i2i

def reduce_mem(df):
    """
    减少一个dataframe的电脑存储内存, 非RNM玩家建议使用, df为pandas的读入的dataframe文件。
    """
    starttime = time.time() 
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
    start_mem = df.memory_usage().sum() / 1024**2
    for col in df.columns:
        col_type = df[col].dtypes
        if col_type in numerics:
            c_min = df[col].min()
            c_max = df[col].max()
            if pd.isnull(c_min) or pd.isnull(c_max):
                continue
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
    end_mem = df.memory_usage().sum() / 1024**2
    print('-- Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction),time spend:{:2.6f} min'.format(end_mem,
                                                                           100*(start_mem-end_mem)/start_mem,
                                                                            (time.time()-starttime)/60))
    return df

#利用分位数将连续值变为离散值
def trans(df, colname, pre_percentile = None, percentile_number = 20):
    l = np.array(df[colname])
    if not pre_percentile:
        percentile = []
        for i in range(1,percentile_number+1):
            percentile.append(np.percentile(l, i*(100//20)))
        d = [-np.inf]+percentile+[np.inf]
        for i in tqdm(range(len(l))):
            for j in range(1,percentile_number+1):
                if d[j-1]<=l[i]<d[j]:
                    l[i]=j-1
                    break
    else:
        d = [-np.inf]+pre_percentile+[np.inf]
        for i in tqdm(range(len(l))):
            for j in range(1,len(pre_percentile)-1):
                if d[j-1]<=l[i]<d[j]:
                    l[i]=j-1
                    break
    df[colname]=l

#余弦相似度
def cosim(x_emb, all_x_emb):
    '''
    x_emb是一个tensor
    '''
    x_all_x = torch.matmul(x_emb,all_x_emb.T)
    norm_l2_x = torch.norm(x_emb, dim = -1, keepdim = True)
    norm_l2_all_x = torch.norm(all_x_emb, dim = -1, keepdim = True)
    down = torch.matmul(norm_l2_x,norm_l2_all_x.T)
    return x_all_x/down